#!/usr/bin/python
# -*- coding: utf8 -*-

from pyparsing import Literal

from expresion import Expresion, Tipo, Exp, TipoExp, TypeException, makeSintaxis

from lenguaje import Extension

class TipoTupla(Tipo):
    
    def __init__(self, toks):
        """
        Un tipo tupla se inicializa por los dos tipo de los elementos
        que componen la tupla.
        """
        comp1, comp2 = toks
        self.comp1 = comp1
        self.comp2 = comp2
    
    def __eq__(self, other):
        """
        Un tipo es igual a un tipo tupla, si en principio es un tipo tupla
        y en segundo lugar si los dos tipos que la componen se corresponden
        con los del otro tipo tupla.
        """
        return self.__class__ == other.__class__ and \
                    self.comp1 == other.comp1 and \
                    self.comp2 == other.comp2
    
    @classmethod
    def sintaxis(cls):
        por = Literal('x').suppress()
        Tupla = makeSintaxis('<', TipoExp, por, TipoExp, '>')   
        return Tupla
    
    def __str__(self):
        s1 = str(self.comp1)
        s2 = str(self.comp2)
        return  "<%s x %s>" % (s1, s2)
        
    
class Tupla(Expresion):
    
    def __init__(self, toks):
       """
       Una tupla se inicializa por dos elementos que la componen.
       """
       elem1, elem2 = toks
       self.elem1 = elem1
       self.elem2 = elem2
    
    def reducir(self):
        """
        El resultado de reducir una tupla es otra
        con ambas componentes reducidas.
        """
        v1 = self.elem1.reducir()
        v2 = self.elem2.reducir()
        return Tupla([v1, v2])
        
    def tipar(self, namespace):
        """
        gamma | > M: alpha, gamma | N: beta
        -----------------------------------
        gamma | > <M,N> : (alpha x beta)
        """
        t1 = self.elem1.tipar(namespace)
        t2 = self.elem2.tipar(namespace)
        return TipoTupla([t1, t2])
        
    def sustituir(self, var, expresion):
        """
        <M,N> {X <- T} --> <M {X <- T}, N {X <- T}>
        """
        s1 = self.elem1.sustituir(var, expresion)
        s2 = self.elem2.sustituir(var, expresion)
        return Tupla([s1, s2])
            
    @classmethod
    def sintaxis(cls):
        Tupla = makeSintaxis('<', Exp, ',',  Exp,'>')
        return Tupla
    
    def __str__(self):
        return "<%s, %s>" % (self.elem1, self.elem2)
        
    
class Proy1Tupla(Expresion):
    
    def __init__(self, toks):
        """
        El proyector de primer componente de tupla, se inicializa
        con una tupla.
        """
        self.tupla = toks[0]
    
    def reducir(self):
        """
        Para reducir un proyector de primer componente
        primero se reduce la tupla que lo compone y
        luego se devuelve la primer componente de dicha tupla. 
        """
        tup = self.tupla.reducir()
        return tup.elem1
        
    def tipar(self, namespace):
        """
        gamma | > T : (alpha x beta)
        ---------------------------
        gamma | > p1(T) : alpha
        """
        t1 = self.tupla.tipar(namespace)
        if isinstance(t1, TipoTupla):
            return t1.comp1
        else:
            raise TypeException(('Termino: %s \n'+ \
                                'Tipo del termino: %s \n'+ \
                                'El tipo del termino no se corresponde con el tipo Tupla.') % (self.tupla,t1))
    
    def sustituir(self, var, expresion):
        """
        p1(E) {X <- T} --> p1(E {X <- T})
        """
        s1 = self.tupla.sustituir(var, expresion)
        return Proy1Tupla([s1])
    
    @classmethod
    def sintaxis(cls):
        Proy1 = makeSintaxis('p1(', Exp, ')')
        return Proy1    
    
    def __str__(self):
        return "p1(%s)" % self.tupla
        
class Proy2Tupla(Expresion):
    
    def __init__(self, toks):
        """
        El proyector de segunda componente de tupla, se inicializa
        con una tupla.
        """
        self.tupla = toks[0]
    
    def reducir(self):
        """
        Para reducir un proyector de segunda componente
        primero se reduce la tupla que lo compone y
        luego se devuelve la segunda componente de dicha tupla. 
        """
        tup = self.tupla.reducir()
        return tup.elem2
        
    def tipar(self, namespace):
        """
        gamma | > T : (alpha x beta)
        ---------------------------
        gamma | > p2(T) : beta
        """
        t1 = self.tupla.tipar(namespace)
        if isinstance(t1, TipoTupla):
            return t1.comp2
        else:
            raise TypeException(('Termino: %s \n'+ \
                                'Tipo del termino: %s \n'+ \
                                'El tipo del termino no se corresponde con el tipo Tupla.') % (self.tupla,t1))
    
    def sustituir(self, var, expresion):
        """
        p12(E) {X <- T} --> p2(E {X <- T})
        """
        s1 = self.tupla.sustituir(var, expresion)
        return Proy2Tupla([s1])
    
    @classmethod
    def sintaxis(cls):
        Proy2 = makeSintaxis('p2(', Exp, ')')
        return Proy2    
    
    def __str__(self):
        return "p2(%s)" % self.tupla

		
extensionTupla = Extension("Tuplas", expresiones=[Tupla, Proy1Tupla, Proy2Tupla], tipos=[TipoTupla])
